using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace SpellCore
{
    class AscensWrapper : ISpellCheckEngine, IDisposable
    {
        public CheckingResult GetWordStatus(string word)
        {
            if (DictionaryContainsNormalizedWord(_excludeDictionaryHandle, word))
            {
                return CheckingResult.Bad;
            }
            if (IsWordSpelledCorrect(word) || IsWordSpelledCorrect(word.Normalize(NormalizationForm.FormC)) ||
                                              IsWordSpelledCorrect(word.Normalize(NormalizationForm.FormD)))
            {
                return CheckingResult.Good;
            }
            return CheckingResult.Unknown;
        }

        private static bool DictionaryContainsNormalizedWord(IntPtr handle, string word)
        {
            return IsWordInDictionary(handle, word, (UInt32)word.Length) ||
                   IsWordInDictionary(handle, word.Normalize(NormalizationForm.FormC), 
                        (UInt32)word.Normalize(NormalizationForm.FormC).Length) ||
                   IsWordInDictionary(handle, word.Normalize(NormalizationForm.FormD), 
                        (UInt32)word.Normalize(NormalizationForm.FormD).Length);
        }

        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string lpFileName);

        internal static IntPtr _library;

        private readonly IntPtr _masterDictionaryHandle;
        private readonly IntPtr _personalDictionaryHandle;
        private readonly IntPtr _excludeDictionaryHandle;

        private static string ExcludeFileName
        {
            get { return "__exclude__"; }
        }

        [DllImport("ascens.dll")]
        private static extern IntPtr LoadDictionary([MarshalAs(UnmanagedType.LPWStr)] string dict_file);
        
        public static AscensWrapper CreateWrapper(string pathToEngineRootDirectory, 
                                                  string baseDictionaryWritingSystemPath, 
                                                  string baseUserDictionaryWritingSystemPath,
                                                  string writingSystemId)
        {
            if (string.IsNullOrEmpty(pathToEngineRootDirectory))
            {
                throw new ArgumentException("File path should not be null or empty.", "pathToEngineRootDirectory");
            }
            if (string.IsNullOrEmpty(baseDictionaryWritingSystemPath))
            {
                throw new ArgumentException("File path should not be null or empty.", "baseDictionaryWritingSystemPath");
            }
            if (string.IsNullOrEmpty(baseDictionaryWritingSystemPath))
            {
                throw new ArgumentException("File path should not be null or empty.", "baseUserDictionaryWritingSystemPath");
            }
            if (string.IsNullOrEmpty(writingSystemId))
            {
                throw new ArgumentException("Writing System Id should not be null or empty.", "writingSystemId");
            }
            
            string dictionaryFile = GetDictionaryFilePath(baseDictionaryWritingSystemPath, writingSystemId);
            if (!File.Exists(dictionaryFile))
            {
                return null;
            }

            string excludeFile = GetDictionaryFilePath(baseDictionaryWritingSystemPath, ExcludeFileName);
            if (!File.Exists(dictionaryFile))
            {
                return null;
            }


            if (_library == IntPtr.Zero)
            {
                _library = LoadLibrary(Path.Combine(pathToEngineRootDirectory, "ascens.dll"));
            }
            if (_library == IntPtr.Zero)
            {
                return null;
            }
            try
            {
                string personalDictionaryFile = GetDictionaryFilePath(baseUserDictionaryWritingSystemPath, writingSystemId);
                return new AscensWrapper(dictionaryFile, personalDictionaryFile, excludeFile);
            }
            catch
            {
                return null;
            }
        }

        private AscensWrapper( string dictionaryFilePath, string personalDictionaryFilePath, string excludeDictionaryFilePath)
        {
             _masterDictionaryHandle = LoadDictionary(dictionaryFilePath);
             if ( _masterDictionaryHandle == IntPtr.Zero)
             {
                 Dispose(false);
                 throw new ApplicationException("Couldn't create Ascens session.");
             }

             _excludeDictionaryHandle = LoadDictionary(excludeDictionaryFilePath);
             if (_excludeDictionaryHandle == IntPtr.Zero)
             {
                 Dispose(false);
                 throw new ApplicationException("Couldn't create Ascens session.");
             }

             EnsureDictionaryExists(personalDictionaryFilePath);

             _personalDictionaryHandle = LoadDictionary(personalDictionaryFilePath);
             if (_personalDictionaryHandle == IntPtr.Zero)
             {
                 Dispose(false);
                 throw new ApplicationException("Couldn't create personal Dictionary for Ascens.");
             }
        }

        //frees spell object
        [DllImport("ascens.dll")]
        private static extern void UnloadDictionary(IntPtr dictionaryHandle);
        
        ~AscensWrapper()
        {
            Dispose(false);
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private bool _isDisposed;
        private void Dispose(bool disposing)
        {
            if(_isDisposed){
                return;
            }
            _isDisposed = true;
            if (disposing)
            {
                // release managed resources
            }
            // release unmanaged resources
            UnloadDictionary(_masterDictionaryHandle);
            UnloadDictionary(_personalDictionaryHandle);
            UnloadDictionary(_excludeDictionaryHandle);
        }

        //spellcheck word, returns 1 if word ok otherwise 0
        [DllImport("ascens.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool IsWordInDictionary(IntPtr hDictionary, 
                                                      [MarshalAs(UnmanagedType.LPWStr)] string word, 
                                                      UInt32 cchWord);

        private bool IsWordSpelledCorrect(string word)
        {
            if (IsWordInDictionary(_excludeDictionaryHandle, word, (UInt32)word.Length))
            {
                return false;
            }
            return IsWordInDictionary(_masterDictionaryHandle, word, (UInt32)word.Length) ||
                   IsWordInDictionary(_personalDictionaryHandle, word, (UInt32)word.Length);
        }

        public bool CanSupportAdditions()
        {
            return true;
        }

        [DllImport("ascens.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool AddWordToDictionary(IntPtr hDictionary,
                                                         [MarshalAs(UnmanagedType.LPWStr)] string szWord,
                                                        UInt32 cchWord);

        [DllImport("ascens.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool RemoveWordFromDictionary(IntPtr hDictionary,
                                                         [MarshalAs(UnmanagedType.LPWStr)] string szWord,
                                                         UInt32 cchWord);

        public void AddWordAsGood(string word)
        {
            if (DictionaryContainsNormalizedWord(_excludeDictionaryHandle, word))
            {
                RemoveWordFromDictionary(_excludeDictionaryHandle, word, (UInt32) word.Length);
            }
            AddWordToDictionary(_personalDictionaryHandle, word, (UInt32)word.Length);
        }

        public bool CanSupportBadWords()
        {
            return true;
        }

        public void AddWordAsBad(string word)
        {
            if (DictionaryContainsNormalizedWord(_masterDictionaryHandle, word))
            {
                RemoveWordFromDictionary(_masterDictionaryHandle, word, (UInt32)word.Length);
            } 
            if (DictionaryContainsNormalizedWord(_personalDictionaryHandle, word))
            {
                RemoveWordFromDictionary(_personalDictionaryHandle, word, (UInt32)word.Length);
            }
            AddWordToDictionary(_excludeDictionaryHandle, word, (UInt32) word.Length);
        }

        public bool CanProvideSuggestions()
        {
            return true;
        }

        [DllImport("ascens.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetSuggestionsFromWord(IntPtr hDictionary,
                                                         [MarshalAs(UnmanagedType.LPWStr)] string szWord, 
                                                         UInt32 cchWord,
                                                         IntPtr szBuffer,
                                                         UInt32 cbBuffer,
                                                         UInt16 nEditDistanceMax,
                                                         UInt16 nBestDistanceMax);

        public IEnumerable<SimilarityCheckResult> GetSuggestions(string word)
        {
            //StringBuilder buffer = new StringBuilder(4096);
            List<SimilarityCheckResult> result = new List<SimilarityCheckResult>();

            const UInt32 cchBuffer = 4096;
            IntPtr buffer = Marshal.AllocHGlobal((int)cchBuffer * Marshal.SizeOf(typeof(UInt16)));

            if (GetSuggestionsFromWord(_personalDictionaryHandle, word, (UInt32)word.Length,
                buffer, cchBuffer, 1, 0))
            {
                GetSuggestionsFromBuffer(buffer, result);
            }
            
            if (GetSuggestionsFromWord(_masterDictionaryHandle, word, (UInt32)word.Length,
                buffer, cchBuffer, 1, (result.Count == 0) ? (ushort)6 : (ushort)0))
            {
                GetSuggestionsFromBuffer(buffer, result);
            }

            Marshal.FreeHGlobal(buffer);
            return result;
        }

        private static void GetSuggestionsFromBuffer(IntPtr buffer, List<SimilarityCheckResult> result) {
            for (;;)
            {
                string suggestion = Marshal.PtrToStringUni(buffer);
                if (suggestion.Length == 0)
                {
                    break;
                }
                result.Add(new SimilarityCheckResult(SimilarityCheckResult.UnknownScore, suggestion));
                int i = (suggestion.Length+1)*Marshal.SizeOf(typeof(UInt16));
                buffer = new IntPtr(buffer.ToInt32()+i);
            }
        }

        public static void CreateDictionary(string pathToEngineRootDirectory,
                                            string baseDictionaryWritingSystemPath, 
                                            string baseUserDictionaryWritingSystemPath, 
                                            string writingSystemId)
        {
            string masterDir = Path.Combine(baseDictionaryWritingSystemPath, "Ascens");

            if (Directory.Exists(masterDir))
            {
                throw new InvalidOperationException(masterDir + " already exists.");
            }

            string masterDictionaryFilePath = GetDictionaryFilePath(baseDictionaryWritingSystemPath, writingSystemId);
            EnsureDictionaryExists(masterDictionaryFilePath);

            string excludeDictionary = GetDictionaryFilePath(baseDictionaryWritingSystemPath, ExcludeFileName);
            EnsureDictionaryExists(excludeDictionary);

            string personalDictionaryFilePath = GetDictionaryFilePath(baseUserDictionaryWritingSystemPath, writingSystemId);
            EnsureDictionaryExists(personalDictionaryFilePath);

            string personalExcludeFilePath = GetDictionaryFilePath(baseUserDictionaryWritingSystemPath, ExcludeFileName);
            EnsureDictionaryExists(personalExcludeFilePath);
        }

        private static void EnsureDictionaryExists(string personalDictionaryFilePath)
        {
            Directory.CreateDirectory(Path.GetDirectoryName(personalDictionaryFilePath));
            if (!File.Exists(personalDictionaryFilePath))
            {
                File.WriteAllText(personalDictionaryFilePath, string.Empty);
            }
        }

        private static string GetDictionaryFilePath(string baseDictionaryWritingSystemPath, string writingSystemId)
        {
            string ascensUserDictionaryPath = Path.Combine(baseDictionaryWritingSystemPath, "Ascens");
            return Path.Combine(ascensUserDictionaryPath, writingSystemId + ".dic");
        }

    }
}
